#!/usr/bin/env node

const { spawn } = require("node:child_process");
const fs = require("fs");
const path = require("path");

const binaryName = process.platform === "win32" ? "codemod.exe" : "codemod";

function detectPackageName() {
  const { platform, arch } = process;

  switch (platform) {
    case "darwin":
      if (arch === "arm64") {
        return "@codemod.com/cli-darwin-arm64";
      }
      if (arch === "x64") {
        return "@codemod.com/cli-darwin-x64";
      }
      break;
    case "linux": {
      const { family, MUSL } = require("detect-libc");
      const libcSuffix =
        family === MUSL ? "musl" : arch === "arm" ? "gnueabihf" : "gnu";
      if (arch === "x64") {
        return `@codemod.com/cli-linux-x64-${libcSuffix}`;
      }
      if (arch === "arm64") {
        return `@codemod.com/cli-linux-arm64-${libcSuffix}`;
      }
      break;
    }
    case "win32":
      if (arch === "x64") {
        return "@codemod.com/cli-win32-x64-msvc";
      }
      if (arch === "arm64") {
        return "@codemod.com/cli-win32-arm64-msvc";
      }
      break;
    default:
      break;
  }

  return null;
}

function failToLocateBinary(pkgName, originalError) {
  const messages = ["Failed to locate the codemod native binary."];

  if (pkgName) {
    messages.push(`Expected package: ${pkgName}`);
  } else {
    messages.push(
      "This platform does not have a published prebuilt codemod binary.",
    );
  }

  if (originalError) {
    messages.push(`Underlying error: ${originalError.message}`);
  }

  messages.push(
    "If you are installing from npm, ensure optional dependencies are enabled (e.g. do not use --no-optional).",
  );
  messages.push(
    "For local development, run `cargo build --package codemod-cli --release` before invoking this script.",
  );

  console.error(messages.join("\n"));
  process.exit(1);
}

function resolveBinaryPath() {
  const pkgName = detectPackageName();
  let moduleError = null;

  if (pkgName) {
    try {
      const pkgDir = path.dirname(
        require.resolve(`${pkgName}/package.json`, { paths: [__dirname] }),
      );
      const candidate = path.join(pkgDir, binaryName);
      if (fs.existsSync(candidate)) {
        return { binaryPath: candidate, pkgName };
      }
    } catch (err) {
      if (err.code !== "MODULE_NOT_FOUND") {
        moduleError = err;
      } else {
        moduleError = err;
      }
    }
  }

  console.log("pkgName", pkgName);

  const releaseCandidate = path.join(
    __dirname,
    "..",
    "..",
    "..",
    "target",
    "release",
    binaryName,
  );
  if (fs.existsSync(releaseCandidate)) {
    return { binaryPath: releaseCandidate, pkgName: null };
  }

  const debugCandidate = path.join(
    __dirname,
    "..",
    "..",
    "..",
    "target",
    "debug",
    binaryName,
  );

  if (fs.existsSync(debugCandidate)) {
    return { binaryPath: debugCandidate, pkgName: null };
  }

  failToLocateBinary(pkgName, moduleError);
}

const { binaryPath } = resolveBinaryPath();

const child = spawn(binaryPath, process.argv.slice(2), {
  stdio: "inherit",
});

child.on("error", (error) => {
  console.error("Failed to execute codemod binary.");
  console.error(error);
  process.exit(1);
});

const signals = ["SIGINT", "SIGTERM", "SIGHUP"];

const forwarders = signals.map((signal) => {
  const handler = () => {
    if (child.killed) {
      return;
    }
    try {
      child.kill(signal);
    } catch (_) {
      // Ignore
    }
  };
  process.on(signal, handler);
  return { signal, handler };
});

child.on("exit", (code, signal) => {
  forwarders.forEach(({ signal, handler }) => {
    process.removeListener(signal, handler);
  });

  if (signal) {
    process.kill(process.pid, signal);
  } else {
    process.exit(code == null ? 1 : code);
  }
});
